const std = @import("std");
const fs = std.fs;
const Compiler = @import("Compiler.zig");

pub fn main() anyerror!void {
    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 2) {
        std.log.err("Usage: {s} <dir/file.jack> [install prefix]", .{args[0]});
        return;
    }

    const filename = args[1];
    const cwd = fs.cwd();

    const source = try open(cwd, filename);
    defer source.close();
    const stat = try source.stat();

    if (stat.kind == .directory) {
        const src_path = filename;
        const dst_path = if (args.len > 2) args[2] else src_path;
        var src_dir = try cwd.openDir(src_path, .{ .iterate = true });
        defer src_dir.close();

        // iterate over the iterableDir to find the .jack files in it
        var iter = src_dir.iterate();
        while (try iter.next()) |entry| {
            if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".jack")) {
                try compile(allocator, src_dir, dst_path, entry.name);
            }
        }
    } else if (stat.kind == .file) {
        if (std.mem.endsWith(u8, filename, ".jack")) {
            const src_path = fs.path.dirname(filename) orelse "./";
            var src_dir = try cwd.openDir(src_path, .{});
            defer src_dir.close();

            const dst_path = if (args.len > 2) args[2] else src_path;
            try compile(allocator, src_dir, dst_path, fs.path.basename(filename));
        }
    } else {
        unreachable;
    }
}

fn compile(gpa: std.mem.Allocator, src_dir: fs.Dir, dst_path: []const u8, name: []const u8) !void {
    const bytes = try src_dir.readFileAlloc(gpa, name, std.math.maxInt(usize));
    defer gpa.free(bytes);

    const output = try std.mem.concat(gpa, u8, &.{ fs.path.stem(name), ".vm" });
    defer gpa.free(output);

    var dst_dir = try fs.cwd().makeOpenPath(dst_path, .{});
    defer dst_dir.close();

    const vmfile = try dst_dir.createFile(output, .{ .truncate = true });
    defer vmfile.close();

    var compiler: Compiler = try .init(gpa, bytes, vmfile.writer());
    defer compiler.deinit();
    compiler.compile("class");
}

// https://github.com/ziglang/zig/issues/16738
// https://github.com/marler8997/windows-coreutils/blob/ee80c7023eb05c5bc7cc15b296aac0528f323d42/ls.zig#L78-L98
fn open(dir: fs.Dir, path: []const u8) !fs.File {
    if (@import("builtin").os.tag == .windows) {
        const windows = std.os.windows;
        const path_w = try windows.sliceToPrefixedFileW(null, path);
        return .{
            .handle = try windows.OpenFile(path_w.span(), .{
                .dir = dir.fd,
                .access_mask = windows.SYNCHRONIZE | windows.GENERIC_READ,
                .creation = windows.FILE_OPEN,
                .filter = .any,
            }),
        };
    }
    return dir.openFile(path, .{});
}
